#region Using Statements

using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Xfinity.Console;
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

#endregion

namespace Xfinity.Engine.Plugins
{
    /// <summary>
    /// A callback function for the event of a keypress.
    /// </summary>
    [Serializable]
    public delegate void KeyEventHandler();
    /// <summary>
    /// A callback function for the event of mouse movement.
    /// </summary>
    [Serializable]
    public delegate void MouseEventHandler();

    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public partial class InputHandler : GameComponent
    {
        /// <summary>
        /// Constructs a new instance of the keyboard listener.
        /// </summary>
        /// <param name="game">The Game object.</param>
        public InputHandler(Game game)
            : base(game)
        {
            //TODO: Make an interfece IInputHandler
            //TODO: UpdateSortOrder to as close as first as possible
            //TODO: look at making a seperate class for the register functions (IInputBinder?)
            game.Services.AddService(typeof(InputHandler), this);
            game.Components.Add(this);
            UpdateOrder = int.MinValue;
        }


        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            GraphicsDevice gd = ((IGraphicsDeviceService)(Game.Services.GetService(typeof(IGraphicsDeviceService)))).GraphicsDevice;
            halfWidth = gd.Viewport.Width / 2;
            halfHeight = gd.Viewport.Height / 2;
            base.Initialize();
        }

        private static KeyboardState oldKeyboardState;
        private static KeyboardState keyboardState;

        public static KeyboardState KeyboardState
        {
            [DebuggerStepThrough]
            get { return keyboardState; }
        }

        public static KeyboardState OldKeyboardState
        {
            [DebuggerStepThrough]
            get { return oldKeyboardState; }
        }
#if !XBOX
        private static MouseState oldMouseState = new MouseState();

        public static MouseState OldMouseState
        {
            [DebuggerStepThrough]
            get { return oldMouseState; }
        }
#endif
        private static Vector2 mousePos;

        public static Vector2 MousePosition
        {
            [DebuggerStepThrough]
            get { return mousePos; }
        }
#if !XBOX
        private static MouseState mouseState = new MouseState();

        public static MouseState MouseState
        {
            [DebuggerStepThrough]
            get { return mouseState; }
        }
#endif
        private static double relativeMousePositionX;

        public static double RelativeMousePositionX
        {
            [DebuggerStepThrough]
            get { return relativeMousePositionX; }
        }
        private static double relativeMousePositionY;

        public static double RelativeMousePositionY
        {
            [DebuggerStepThrough]
            get { return relativeMousePositionY; }
        }


        private static int halfWidth, halfHeight;
        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {



            // get states
            oldKeyboardState = keyboardState;
            keyboardState = Keyboard.GetState();

            oldMouseState = mouseState;

            mouseState = Mouse.GetState();
            if (!Game.IsActive)
            {
                return;
            }
            if (!StandardConsole.ConsoleShowing)
            {
                // test the mouse
                if (mouseState.X != halfWidth)
                {
                    System.Diagnostics.Debug.WriteLine(relativeMousePositionX.ToString(), "Mouse moved X");
                    relativeMousePositionX = mouseState.X / (double)GraphicsHelper.GraphicsDevice.Viewport.Width;
                    relativeMousePositionX *= 2;
                    relativeMousePositionX -= 1;
                    mousePos.X = (float)relativeMousePositionX;
                    mouseMovedX();
                }
                else
                {
                    relativeMousePositionX = 0;
                    mousePos.X = 0;
                    mouseXCenter();
                }
                if (mouseState.Y != halfHeight)
                {
                    System.Diagnostics.Debug.WriteLine(relativeMousePositionY.ToString(), "Mouse moved Y");
                    relativeMousePositionY = mouseState.Y / (double)GraphicsHelper.GraphicsDevice.Viewport.Height;
                    relativeMousePositionY *= 2;
                    relativeMousePositionY -= 1;
                    mousePos.Y = (float)relativeMousePositionY;
                    mouseMovedY();
                }
                else
                {
                    relativeMousePositionY = 0;
                    mousePos.Y = 0;
                    mouseYCenter();
                }

                if (mousePos != Vector2.Zero)
                {
                    Mouse.SetPosition(halfWidth, halfHeight);
                }
            }

            if (!StandardConsole.RedirectInputToConsole)
            {
                // loop through each of the 'key down' keys being watched
                foreach (KeyValuePair<Keys, KeyEventHandler> action in keysDown)
                {
                    // test if the key has just been pressed
                    if (keyboardState.IsKeyDown(action.Key) && (oldKeyboardState.IsKeyUp(action.Key)))
                    {
                        System.Diagnostics.Debug.WriteLine(action.Key + " down.", gameTime.TotalGameTime.ToString());
                        // run the KeyEvent
                        action.Value();
                    }
                }

                // loop through each of the 'key press' keys being watched
                foreach (KeyValuePair<Keys, KeyEventHandler> action in keysPress)
                {
                    // test if the key has just been pressed
                    if (keyboardState.IsKeyDown(action.Key) && (oldKeyboardState.IsKeyDown(action.Key)))

                        // run the KeyEvent
                        action.Value();
                }

                // loop through each of the 'key up' keys being watched
                foreach (KeyValuePair<Keys, KeyEventHandler> action in keysUp)
                {
                    // check the key to see if it has just been released
                    if ((oldKeyboardState.IsKeyDown(action.Key) && keyboardState.IsKeyUp(action.Key)))
                    {
                        System.Diagnostics.Debug.WriteLine(action.Key + " up.", gameTime.TotalGameTime.ToString());

                        // run the event handler
                        action.Value();
                    }
                }


            }
            else
            {
                #region Writing at the command line.
                // writing at the command line; so this doesn't need to be optimised
                if (IsKeyJustPressed(Keys.OemTilde))
                {
                    DefaultCommands.ToggleConsole(null);
                    return;
                }
                else if (IsKeyJustPressed(Keys.Enter))
                {
                    string line = StandardConsole.InputLine.ToString();
                    StandardConsole.Transcript.Add(line);
                    StandardConsole.InputLine.Remove(0, StandardConsole.InputLine.Length);
                    try
                    {
                        CommandParser.ProcessCommand(line);
                    }
                    catch (CommandFailedException e)
                    {
                        StandardConsole.WriteLine(e.Commands[0].ToString() + ": Invalid command.");
                    }


                }
                else if (IsKeyJustPressed(Keys.Back))
                {
                    if (StandardConsole.InputLine.Length >= 1)
                    {
                        StandardConsole.InputLine.Remove(StandardConsole.InputLine.Length - 1, 1);
                    }
                }
                else if (IsKeyJustPressed(Keys.Space))
                {
                    StandardConsole.InputLine.Append(' ');
                }
                else
                {
                    foreach (Keys key in keyboardState.GetPressedKeys())
                    {
                        if (oldKeyboardState.IsKeyDown(key) || key == Keys.LeftShift || key == Keys.RightShift)
                        {
                            continue;
                        }
                        string s;
                        bool number;
                        switch (key)
                        {
                            case Keys.D0:
                            case Keys.D1:
                            case Keys.D2:
                            case Keys.D3:
                            case Keys.D4:
                            case Keys.D5:
                            case Keys.D6:
                            case Keys.D7:
                            case Keys.D8:
                            case Keys.D9:
                                {
                                    s = key.ToString().Substring(1);
                                    number = true;
                                    break;
                                }
                            default:
                                {
                                    s = key.ToString();
                                    number = false;
                                    break;
                                }
                        }
                        if (key.ToString().Length == 1 || number)
                        {
                            if (keyboardState.IsKeyDown(Keys.LeftShift) || keyboardState.IsKeyDown(Keys.RightShift))
                            {
                                StandardConsole.InputLine.Append(s.ToUpper());
                            }
                            else
                            {
                                StandardConsole.InputLine.Append(s.ToLower());
                            }
                        }
                    }
                }
                #endregion
            }

        }




        private static bool IsKeyJustPressed(Keys k)
        {
            return keyboardState.IsKeyDown(k) && !oldKeyboardState.IsKeyDown(k);
        }
        /// <summary>
        /// Registers a key with both up and down events.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="keyDown">The action to take when the key is pressed.</param>
        /// <param name="keyUp">The action to take when the key is released.</param>
        [DebuggerStepThrough]
        public static void RegisterKey(Keys key, KeyEventHandler keyDown, KeyEventHandler keyUp)
        {
            RegisterKeyDown(key, keyDown);
            RegisterKeyUp(key, keyUp);
        }
        /// <summary>
        /// Registers a key with both up and down events, and for being held.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="keyDown">The action to take when the key is pressed.</param>
        /// /// <param name="keyPress">The action to take when the key is held down.</param>
        /// <param name="keyUp">The action to take when the key is released.</param>
        [DebuggerStepThrough]
        public static void RegisterKey(Keys key, KeyEventHandler keyDown, KeyEventHandler keyPress, KeyEventHandler keyUp)
        {
            RegisterKeyDown(key, keyDown);
            RegisterKeyPress(key, keyPress);
            RegisterKeyUp(key, keyUp);
        }
        /// <summary>
        /// Register an action to be taken when the specified key is held down.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="handler">The action to take.</param>
        [DebuggerStepThrough]
        private static void RegisterKeyPress(Keys key, KeyEventHandler handler)
        {
            if (keysPress.ContainsKey(key))
            {
                keysPress[key] += handler;
                System.Diagnostics.Trace.WriteLine("Added a new listener for " + key, "Key Press registered");
            }
            else
            {
                keysPress.Add(key, handler);
                System.Diagnostics.Trace.WriteLine("Created listener for " + key, "Key Press registered");
            }
        }

        /// <summary>
        /// Binds a key to a command.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="command">The command.</param>
        public static void BindToCommand(Keys key, string command)
        {
            CommandStruct c = new CommandStruct();
            c.command = command;
            boundCommands.Add(new KeyMapping(key, command));
            RegisterKeyDown(key, c.Execute);//delegate
            //{
            //    CommandParser.ProcessCommand(command);
            //});
        }
        public struct KeyMapping
        {
            private Keys key;

            /// <summary>
            /// 
            /// </summary>
            public Keys Key
            {
                get { return key; }
                set { key = value; }
            }
            private string command;

            /// <summary>
            /// 
            /// </summary>
            public string Command
            {
                get { return command; }
                set { command = value; }
            }
            public KeyMapping(Keys key, string command)
            {
                this.key = key;
                this.command = command;
            }
        }
        private static List<KeyMapping> boundCommands = new List<KeyMapping>();

        /// <summary>
        /// 
        /// </summary>
        public static List<KeyMapping> BoundCommands
        {
            get { return boundCommands; }
            set { boundCommands = value; }
        }

        [Serializable]
        struct CommandStruct
        {
            public string command;
            public void Execute()
            {
                CommandParser.ProcessCommand(command);
            }
        }
        /// <summary>
        /// Register an action to be taken when the specified key is pressed.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="handler">The action to take.</param>
        [DebuggerStepThrough]
        public static void RegisterKeyDown(Keys key, KeyEventHandler handler)
        {
            if (keysDown.ContainsKey(key))
            {
                keysDown[key] += handler;
                System.Diagnostics.Trace.WriteLine("Added a new listener for " + key, "Key Down registered");
            }
            else
            {
                keysDown.Add(key, handler);
                System.Diagnostics.Trace.WriteLine("Created listener for " + key, "Key Down registered");
            }
        }
        [DebuggerStepThrough]
        public static void RegisterMouseMovedX(MouseEventHandler handler)
        {
            mouseMovedX += handler;
        }
        [DebuggerStepThrough]
        public static void RegisterMouseMovedY(MouseEventHandler handler)
        {
            mouseMovedY += handler;
        }
        [DebuggerStepThrough]
        public static void RegisterMouseMovement(MouseEventHandler handler)
        {
            mouseMovedX += handler;
            mouseMovedY += handler;
        }
        [DebuggerStepThrough]
        public static void RegisterMouseXCentered(MouseEventHandler handler)
        {
            mouseXCenter += handler;
        }
        [DebuggerStepThrough]
        public static void RegisterMouseYCentered(MouseEventHandler handler)
        {
            mouseYCenter += handler;
        }
        [DebuggerStepThrough]
        public static void RegisterMouseCentered(MouseEventHandler handler)
        {
            mouseXCenter += handler;
            mouseYCenter += handler;
        }
        /// <summary>
        /// Register an action to be taken when the specified key is released.
        /// </summary>
        /// <param name="key">The key to watch.</param>
        /// <param name="handler">The action to take.</param>
        [DebuggerStepThrough]
        public static void RegisterKeyUp(Keys key, KeyEventHandler handler)
        {
            if (keysUp.ContainsKey(key))
            {
                keysUp[key] += handler;
                System.Diagnostics.Trace.WriteLine("Added a new listener for " + key, "Key Up registered");
            }
            else
            {
                keysUp.Add(key, handler);
                System.Diagnostics.Trace.WriteLine("Created listener for " + key, "Key Up registered");
            }
        }
        public static void SaveMappings(string filename)
        {
            try
            {
                SaveMappings(File.Open(filename, FileMode.Create, FileAccess.Write));
            }
            catch (IOException)
            {
                StandardConsole.Fail("Error: Mappings file already in use.");
            }

        }

        [BindCommand("SaveKeyMappings", BoundKey = Keys.F1)]
        public static void SaveMappingsCommand(string[] commands)
        {
            SaveMappings(@".\keys.xml");
        }

        public static void SaveMappings(Stream stream)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<KeyMapping>));

            serializer.Serialize(stream, boundCommands);

        }

        private static Dictionary<Keys, KeyEventHandler> keysDown = new Dictionary<Keys, KeyEventHandler>();
        private static Dictionary<Keys, KeyEventHandler> keysPress = new Dictionary<Keys, KeyEventHandler>();
        private static Dictionary<Keys, KeyEventHandler> keysUp = new Dictionary<Keys, KeyEventHandler>();
        private static MouseEventHandler mouseMovedX = delegate { };
        private static MouseEventHandler mouseMovedY = delegate { };
        private static MouseEventHandler mouseXCenter = delegate { };
        private static MouseEventHandler mouseYCenter = delegate { };

    }

}